﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Fusion;

namespace Fusion
{
    public class Entity
    {
        #region Fields

        public string name = "Entity";
		public bool active = true;

        public Entity container;
        public Entity Root
        {
            get
            {
                Entity iter = this;

                // Iterate upwards to the last entity
                while( iter.container != null )
                    iter = iter.container;

                return iter;
            }
        } // Accessor to the root entity

        public Vector2 position = Vector2.Zero;
        public Vector2 Position
        { get { return AbsolutePoint( position ); } }

		public Vector2 size = Vector2.One * 8;
		public Vector2 Size
		{
			get { return AbsoluteScale( size ); }
		}

        public Vector2 scale = Vector2.One;
        public Vector2 Scale
        { get { return AbsoluteScale( scale ); } }

        public float angle = 0;
        public float Angle
        { get { return AbsoluteAngle( angle ); } }

		public Vector2 TL
		{ get { return Position; } }
		public Vector2 BR
		{ get { return Position + Size; } }
        
        #endregion

        #region Events

        public event EntityDelegate Updated;
        public event EntityDelegate Drew;
        public event EntityDelegate Destroyed;

        #endregion

        #region Constructors

        /// <summary> Blank Constructor </summary>
        public Entity()
        {
            name = "Entity";
        }

        /// <summary> XML Element Constructor </summary>
        /// <param name="element">XML Element to construct from</param>
        public Entity( XElement element )
        {
            ReadFrom( element );
        }

        /// <summary> Copy Constructor </summary>
        public Entity( Entity copy )
        { Copy( copy ); }

        /// <summary> Copies the original entity </summary>
        /// <param name="original">Entity to copy from</param>
        /// <returns>this</returns>
        public virtual Entity Copy( Entity original )
        {
            name = original.name;
            container = original.container;

            position = original.position;
            scale = original.scale;
            angle = original.angle;

            return this;
        }

		public virtual Entity Clone()
		{
			return new Entity( this );
		}

        #endregion

        #region Methods

        /// <summary> Update method </summary>
        public virtual void Update()
        {
            if( Updated != null )
                Updated( this );
        }

        ///<summary> Draw method </summary>
        public virtual void Draw()
        {
            if( Drew != null )
                Drew( this );
		}

		public void Draw( Vector2 drawPosition )
		{
			Vector2 realPosition = position;
			position = drawPosition;

			Draw();

			position = realPosition;
		}

        ///<summary> Draws using specified transform information </summary>
        public void Draw( Vector2 drawPosition, Vector2 drawScale, float drawAngle )
        {
            Entity realContainer = container;
            Vector2 realPosition = position;
            Vector2 realScale = scale;
            float realAngle = angle;

            position = drawPosition;
            scale = drawScale;
            angle = drawAngle;
			container = null;

            Draw();

            position = realPosition;
            scale = realScale;
            angle = realAngle;
			container = realContainer;
		}

        /// <summary> Draw projected through a window entity </summary>
        /// <param name="window">Entity to use as a temporary container</param>
        public void Draw( Entity window )
        {
            Entity realContainer = container;
            container = window;

            Draw();

            container = realContainer;
        }

        /// <summary> Destroys the entity </summary>
        public virtual void Destroy()
        {
			active = false;
            if( Destroyed != null )
                Destroyed( this );
        }

        #endregion

        #region World

        /// <summary> Calculates the world position of a point relative to this entity </summary>
        /// <param name="localPoint">The point to transform</param>
        /// <returns>Point relative to world space</returns>
        public Vector2 AbsolutePoint( Vector2 localPoint )
        {
            // No container
            if( container == null )
                return localPoint;

            if( container.Angle == 0 )
                return container.Position + (localPoint * container.Scale);
            else
                return localPoint.Transform( container.Position, container.Scale, container.Angle );
        }

        /// <summary> Calculates the local position of a point relative to the root </summary>
        /// <param name="worldPoint">The world point to transform</param>
        /// <returns>Point relative to local space</returns>
        public Vector2 RelativePoint( Vector2 worldPoint )
        {
            // Not quite - this needs rotation and scale support
            return worldPoint - Position;
        }

        /// <summary> Calculates the world position of a point relative to this entity </summary>
        /// <param name="localScale">The local scale to transform</param>
        /// <returns>Scale relative to world space</returns>
        public Vector2 AbsoluteScale( Vector2 localScale )
        {
            if( container == null )
                return localScale;
            else
                return container.Scale * localScale;
        }

        /// <summary> Calculates the local scale of a scale relative to the root </summary>
        /// <param name="worldPoint">The world point to transform</param>
        /// <returns>Point relative to local space</returns>
        public Vector2 RelativeScale( Vector2 worldScale )
        {
            // This is fail altogether
            return worldScale;
        }

        /// <summary> Calculates the world angle of an relative to this entity </summary>
        /// <param name="localAngle">The local angle to transform</param>
        /// <returns>Angle relative to world space</returns>
        public float AbsoluteAngle( float localAngle )
        {
            if( container == null )
                return localAngle;
            else
                return container.Angle + localAngle;
        }

        /// <summary> Calculates the local angle of an angle relative to the root </summary>
        /// <param name="worldPoint">The world angle to transform</param>
        /// <returns>Angle relative to local space</returns>
        public float RelativeAngle( float worldAngle )
        { return worldAngle - Angle; }


        /// <summary> Releases the entity from it's container </summary>
        public Entity MakeAbsolute()
        {
            Vector2 newPos = Position;
            Vector2 newScale = Scale;
            float newAngle = Angle;

            container = null;
            position = newPos;
            scale = newScale;
            angle = newAngle;

            return this;
        }

        /// <summary> Inserts entity into target container </summary>
        /// <param name="other">The entity to use as the new container</param>
        public Entity MakeRelative( Entity target )
        {
            Vector2 newPos = target.RelativePoint( Position );
            Vector2 newScale = target.RelativeScale( Scale );
            float newAngle = target.RelativeAngle( Angle );

            container = target;
            position = newPos;
            scale = newScale;
            angle = newAngle;

            return this;
        }

        #endregion


		#region Intersection

		public virtual bool Intersect( Vector2 point, ref Vector2 projection )
		{
			if( size.X > 0 && size.Y > 0 )
				return Hax.Intersect( point, Position, Size, ref projection );
			else if( point == Position )
				return true;
			else
				return false;
		}

		public virtual bool Intersect( Vector2 boxCenter, Vector2 boxSize, ref Vector2 projection )
		{
			return Hax.Intersect( Position, Size, boxCenter, boxSize, ref projection );
		}

		public virtual bool Intersect( Vector2 from, Vector2 to, ref Vector2 point, ref float slice )
		{
			// write this
			return false;
		}

		public virtual bool Intersect( Entity other, ref Vector2 projection )
		{
			if( other.Position == Position )
				return true;
			else
				return false;
		}
		public virtual Entity TestMovement( Vector2 movement, ref Vector2 projection, List<Entity> collisionTests )
		{
			position += movement;
			Entity ret = null;

			Vector2 testProj = Vector2.One * 99999999;
			foreach( Entity ent in collisionTests )
			{
				if( ent != this && Intersect( ent, ref testProj ) )
					if( testProj.Length() > projection.Length() )
					{
						projection = testProj;
						ret = ent;
					}
			}

			position -= movement;
			return ret;
		}

		#endregion


        #region Xml

        ///<summary> Returns an XML node containing this entity </summary>
		public void AddXml( XElement parent )
		{
			if( parent == null )
				return;

			XElement child = Secretary.AddChild( parent, XmlName );
			WriteXml( child );
		}

		public virtual string XmlName { get { return "Entity"; } }

        /// <summary> Writes all properties into the specified XML element </summary>
        /// <param name="element">The element to write properties to</param>
        public virtual void WriteXml( XElement element )
		{
			if( element == null )
				return;

			element.SetAttributeValue( "position", Secretary.Write( position ) );
			element.SetAttributeValue( "scale", Secretary.Write( scale ) );
			element.SetAttributeValue( "angle", angle.ToString() );
        }

        ///<summary> Reads this entity from an xml element </summary>
        public virtual void ReadFrom( XElement element )
        {
            if( element == null )
                return;

			position = Secretary.ReadVector2( element.Attribute( "position" ).Value );
			scale = Secretary.ReadVector2( element.Attribute( "scale" ).Value );
			angle = Secretary.ReadFloat( element.Attribute( "angle" ).Value );
        }

        #endregion
    }
}

